<!DOCTYPE html>
<html lang="zh-CN">
<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>Grid组件编辑器</title>
  <link rel="stylesheet" href="https://fonts.googleapis.com/css2?family=Inter:wght@300;400;500;600;700&display=swap">
  <link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.4.0/css/all.min.css">
  <script src="https://cdn.jsdelivr.net/npm/vue@3/dist/vue.global.js"></script>
  <style>
    * {
      margin: 0;
      padding: 0;
      box-sizing: border-box;
      font-family: 'Inter', -apple-system, BlinkMacSystemFont, 'Segoe UI', sans-serif;
    }
    
    body {
      background: linear-gradient(135deg, #f5f7fa 0%, #e4edf5 100%);
      min-height: 100vh;
      padding: 20px;
      color: #333;
    }
    
    .container {
      max-width: 1200px;
      margin: 0 auto;
    }
    
    header {
      text-align: center;
      margin-bottom: 40px;
      padding-top: 20px;
    }
    
    h1 {
      font-size: 2.5rem;
      color: #2c3e50;
      margin-bottom: 10px;
      font-weight: 700;
    }
    
    .subtitle {
      font-size: 1.1rem;
      color: #7f8c8d;
      max-width: 700px;
      margin: 0 auto;
      line-height: 1.6;
    }
    
    .content {
      display: grid;
      grid-template-columns: 1fr 1fr;
      gap: 30px;
    }
    
    .editor-panel, .preview-panel {
      background: white;
      border-radius: 16px;
      box-shadow: 0 10px 30px rgba(0, 0, 0, 0.08);
      padding: 25px;
    }
    
    .panel-title {
      display: flex;
      align-items: center;
      margin-bottom: 25px;
      padding-bottom: 15px;
      border-bottom: 1px solid #eee;
    }
    
    .panel-title i {
      font-size: 1.3rem;
      margin-right: 12px;
      color: #3498db;
    }
    
    .panel-title h2 {
      font-size: 1.4rem;
      color: #2c3e50;
    }
    
    .form-group {
      margin-bottom: 25px;
    }
    
    label {
      display: block;
      margin-bottom: 8px;
      font-weight: 500;
      color: #2c3e50;
    }
    
    .slider-container {
      padding: 5px 0;
    }
    
    input[type="range"] {
      width: 100%;
      height: 8px;
      -webkit-appearance: none;
      background: #e0e7ff;
      border-radius: 10px;
      outline: none;
    }
    
    input[type="range"]::-webkit-slider-thumb {
      -webkit-appearance: none;
      width: 22px;
      height: 22px;
      border-radius: 50%;
      background: #3498db;
      cursor: pointer;
      box-shadow: 0 2px 8px rgba(52, 152, 219, 0.4);
    }
    
    .cols-display {
      text-align: center;
      font-weight: 600;
      color: #3498db;
      margin-top: 8px;
      font-size: 1.1rem;
    }
    
    .grid-list-editor {
      margin-top: 30px;
    }
    
    .image-list-editor {
      display: grid;
      grid-template-columns: 1fr;
      gap: 20px;
    }
    
    .image-item {
      background: #f8fafc;
      border: 1px solid #e2e8f0;
      border-radius: 12px;
      padding: 18px;
      transition: all 0.3s ease;
      position: relative;
      display: flex;
      flex-direction: column;
    }
    
    .image-item:hover {
      border-color: #cbd5e1;
      box-shadow: 0 5px 15px rgba(0, 0, 0, 0.05);
    }
    
    .image-item.dragging {
      opacity: 0.7;
      border: 2px dashed #3498db;
    }
    
    .thumbnail-container {
      position: relative;
      width: 100%;
      height: 120px;
      border-radius: 8px;
      overflow: hidden;
      background: #f1f5f9;
      display: flex;
      align-items: center;
      justify-content: center;
      margin-bottom: 15px;
      cursor: pointer;
      border: 1px dashed #cbd5e1;
      transition: all 0.2s;
    }
    
    .thumbnail-container:hover {
      border-color: #3498db;
    }
    
    .thumbnail-container img {
      max-width: 100%;
      max-height: 100%;
      object-fit: contain;
    }
    
    .upload-label {
      position: absolute;
      bottom: 10px;
      right: 10px;
      background: rgba(255, 255, 255, 0.9);
      padding: 4px 10px;
      border-radius: 20px;
      font-size: 0.8rem;
      color: #3498db;
      cursor: pointer;
      display: flex;
      align-items: center;
      gap: 5px;
    }
    
    .upload-label i {
      font-size: 0.9rem;
    }
    
    .image-actions {
      display: flex;
      justify-content: flex-end;
      gap: 12px;
      margin-top: 15px;
      padding-top: 15px;
      border-top: 1px solid #eee;
    }
    
    .image-actions button {
      background: #f1f5f9;
      border: none;
      width: 36px;
      height: 36px;
      border-radius: 50%;
      display: flex;
      align-items: center;
      justify-content: center;
      cursor: pointer;
      color: #64748b;
      transition: all 0.2s;
    }
    
    .image-actions button:hover {
      background: #e2e8f0;
      color: #3498db;
    }
    
    .image-actions button:disabled {
      opacity: 0.5;
      cursor: not-allowed;
    }
    
    .image-actions .delete-btn:hover {
      background: #fee2e2;
      color: #ef4444;
    }
    
    .add-btn {
      background: #3498db;
      color: white;
      border: none;
      border-radius: 10px;
      padding: 14px 20px;
      font-size: 1rem;
      font-weight: 500;
      cursor: pointer;
      display: flex;
      align-items: center;
      justify-content: center;
      gap: 10px;
      width: 100%;
      transition: all 0.3s;
      margin-top: 10px;
    }
    
    .add-btn:hover {
      background: #2980b9;
      transform: translateY(-2px);
      box-shadow: 0 4px 12px rgba(52, 152, 219, 0.3);
    }
    
    .grid-preview {
      margin-top: 20px;
      border: 1px dashed #e2e8f0;
      border-radius: 12px;
      padding: 20px;
      background: #f8fafc;
      min-height: 400px;
    }
    
    .grid {
      display: grid;
      gap: 20px;
    }
    
    .grid-item {
      background: white;
      border-radius: 12px;
      overflow: hidden;
      box-shadow: 0 4px 6px rgba(0, 0, 0, 0.05);
      transition: all 0.3s ease;
      display: flex;
      flex-direction: column;
      height: 100%;
    }
    
    .grid-item:hover {
      transform: translateY(-5px);
      box-shadow: 0 10px 20px rgba(0, 0, 0, 0.1);
    }
    
    .grid-image {
      width: 100%;
      height: 160px;
      background: #f1f5f9;
      display: flex;
      align-items: center;
      justify-content: center;
    }
    
    .grid-image img {
      max-width: 80%;
      max-height: 80%;
      object-fit: contain;
    }
    
    .grid-text {
      padding: 18px;
      text-align: center;
      font-weight: 500;
      color: #2c3e50;
      flex-grow: 1;
      display: flex;
      align-items: center;
      justify-content: center;
    }
    
    .code-block {
      background: #2d3748;
      color: #e2e8f0;
      border-radius: 10px;
      padding: 20px;
      font-family: 'Fira Code', monospace;
      font-size: 0.9rem;
      margin-top: 30px;
      overflow-x: auto;
    }
    
    .code-header {
      display: flex;
      justify-content: space-between;
      align-items: center;
      margin-bottom: 15px;
    }
    
    .copy-btn {
      background: #4a5568;
      color: white;
      border: none;
      padding: 6px 14px;
      border-radius: 6px;
      cursor: pointer;
      font-size: 0.85rem;
      transition: background 0.2s;
    }
    
    .copy-btn:hover {
      background: #3498db;
    }
    
    .drag-handle {
      position: absolute;
      top: 15px;
      left: 15px;
      cursor: move;
      color: #94a3b8;
      font-size: 1.2rem;
    }
    
    .drag-handle:hover {
      color: #3498db;
    }
    
    @media (max-width: 900px) {
      .content {
        grid-template-columns: 1fr;
      }
    }
  </style>
</head>
<body>
  <div id="app">
    <div class="container">
      <header>
        <h1>Grid 宫格组件编辑器</h1>
        <p class="subtitle">通过此编辑器可以配置宫格组件的列数及内容项。添加新选项、上传图片并调整顺序，实时预览效果。</p>
      </header>
      
      <div class="content">
        <div class="editor-panel">
          <div class="panel-title">
            <i class="fas fa-sliders-h"></i>
            <h2>组件配置</h2>
          </div>
          
          <div class="form-group">
            <label>列数设置: <span class="cols-display">{{ cols }}</span></label>
            <div class="slider-container">
              <input type="range" min="1" max="6" v-model.number="cols">
            </div>
          </div>
          
          <div class="grid-list-editor">
            <div class="panel-title">
              <i class="fas fa-th-list"></i>
              <h2>宫格内容项</h2>
            </div>
            
            <div class="image-list-editor">
              <div v-for="(item, index) in list" 
                   :key="item.id"
                   class="image-item"
                   :class="{ 'dragging': draggingIndex === index }"
                   draggable="true"
                   @dragstart="dragStart(index)"
                   @dragover.prevent="dragOver(index)"
                   @dragenter.prevent
                   @drop="drop(index)">
                <div class="drag-handle" @dragstart="dragStart(index)">
                  <i class="fas fa-grip-lines"></i>
                </div>
                
                <div class="thumbnail-container" @click="triggerFileInput(index)">
                  <img :src="item.url" class="thumbnail">
                  <span class="upload-label">
                    <i class="fas fa-upload"></i> 更换图片
                  </span>
                  <input type="file" :ref="'fileInput' + index" style="display: none" 
                         @change="(e) => handleImageUpload(e, index)" accept="image/*">
                </div>
                
                <input type="text" v-model="item.text" placeholder="输入项目名称" class="text-input">
                
                <div class="image-actions">
                  <button @click="moveItem(index, -1)" :disabled="index === 0" title="上移">
                    <i class="fas fa-arrow-up"></i>
                  </button>
                  <button @click="moveItem(index, 1)" :disabled="index === list.length - 1" title="下移">
                    <i class="fas fa-arrow-down"></i>
                  </button>
                  <button @click="removeItem(index)" title="删除" class="delete-btn">
                    <i class="fas fa-trash"></i>
                  </button>
                </div>
              </div>
            </div>
            
            <button class="add-btn" @click="addItem">
              <i class="fas fa-plus"></i> 添加新选项
            </button>
          </div>
        </div>
        
        <div class="preview-panel">
          <div class="panel-title">
            <i class="fas fa-eye"></i>
            <h2>实时预览</h2>
          </div>
          
          <div class="grid-preview">
            <div class="grid" :style="{ 'grid-template-columns': `repeat(${cols}, 1fr)` }">
              <div class="grid-item" v-for="item in list" :key="item.id">
                <div class="grid-image">
                  <img :src="item.url" :alt="item.text">
                </div>
                <div class="grid-text">
                  {{ item.text }}
                </div>
              </div>
            </div>
          </div>
          
          <div class="code-block">
            <div class="code-header">
              <h3>配置数据</h3>
              <button class="copy-btn" @click="copyCode">
                <i class="fas fa-copy"></i> 复制代码
              </button>
            </div>
            <pre>{{ codeSnippet }}</pre>
          </div>
        </div>
      </div>
    </div>
  </div>

  <script>
    const { createApp, ref, computed, watch } = Vue;
    
    createApp({
      setup() {
        const cols = ref(3);
        const list = ref([
          { id: 1, text: '美食外卖', url: 'https://images.unsplash.com/photo-1565299624946-b28f40a0ae38?ixlib=rb-4.0.3&auto=format&fit=crop&w=800&q=80' },
          { id: 2, text: '购物商城', url: 'https://images.unsplash.com/photo-1607082350899-7e105aa886ae?ixlib=rb-4.0.3&auto=format&fit=crop&w=800&q=80' },
          { id: 3, text: '出行服务', url: 'https://images.unsplash.com/photo-1503376780353-7e6692767b70?ixlib=rb-4.0.3&auto=format&fit=crop&w=800&q=80' },
          { id: 4, text: '酒店旅游', url: 'https://images.unsplash.com/photo-1566073771259-6a8506099945?ixlib=rb-4.0.3&auto=format&fit=crop&w=800&q=80' },
          { id: 5, text: '生活缴费', url: 'https://images.unsplash.com/photo-1584302179602-e4c3d3fd629d?ixlib=rb-4.0.3&auto=format&fit=crop&w=800&q=80' },
          { id: 6, text: '医疗健康', url: 'https://images.unsplash.com/photo-1516549655169-df83a0774514?ixlib=rb-4.0.3&auto=format&fit=crop&w=800&q=80' }
        ]);
        
        const draggingIndex = ref(null);
        
        // 添加新选项
        const addItem = () => {
          const newId = Date.now();
          list.value.push({
            id: newId,
            text: `新项目 ${list.value.length + 1}`,
            url: 'https://images.unsplash.com/photo-1546069901-ba9599a7e63c?ixlib=rb-4.0.3&auto=format&fit=crop&w=800&q=80'
          });
        };
        
        // 删除选项
        const removeItem = (index) => {
          if (list.value.length > 1) {
            list.value.splice(index, 1);
          } else {
            alert('至少需要保留一个选项');
          }
        };
        
        // 移动选项位置
        const moveItem = (index, direction) => {
          if ((direction === -1 && index === 0) || 
              (direction === 1 && index === list.value.length - 1)) {
            return;
          }
          
          const newIndex = index + direction;
          const item = list.value[index];
          list.value.splice(index, 1);
          list.value.splice(newIndex, 0, item);
        };
        
        // 触发文件选择
        const triggerFileInput = (index) => {
          const input = document.querySelector(`input[ref="fileInput${index}"]`);
          if (input) input.click();
        };
        
        // 处理图片上传
        const handleImageUpload = (event, index) => {
          const file = event.target.files[0];
          if (!file) return;
          
          if (!file.type.match('image.*')) {
            alert('请选择图片文件');
            return;
          }
          
          const reader = new FileReader();
          reader.onload = (e) => {
            list.value[index].url = e.target.result;
          };
          reader.readAsDataURL(file);
          
          // 重置input
          event.target.value = '';
        };
        
        // 拖拽开始
        const dragStart = (index) => {
          draggingIndex.value = index;
        };
        
        // 拖拽经过
        const dragOver = (index) => {
          if (draggingIndex.value !== null && draggingIndex.value !== index) {
            const draggedItem = list.value[draggingIndex.value];
            list.value.splice(draggingIndex.value, 1);
            list.value.splice(index, 0, draggedItem);
            draggingIndex.value = index;
          }
        };
        
        // 拖拽放置
        const drop = () => {
          draggingIndex.value = null;
        };
        
        // 复制代码
        const copyCode = () => {
          const code = document.querySelector('pre').textContent;
          navigator.clipboard.writeText(code).then(() => {
            alert('代码已复制到剪贴板！');
          });
        };
        
        // 生成代码片段
        const codeSnippet = computed(() => {
          return `{
  cols: ${cols.value},
  list: [
${list.value.map(item => `    { 
      id: ${item.id}, 
      text: "${item.text}", 
      url: "${item.url}" 
    }`).join(',\n')}
  ]
}`;
        });
        
        return {
          cols,
          list,
          draggingIndex,
          addItem,
          removeItem,
          moveItem,
          triggerFileInput,
          handleImageUpload,
          dragStart,
          dragOver,
          drop,
          copyCode,
          codeSnippet
        };
      }
    }).mount('#app');
  </script>
</body>
</html>